import { DeepPartial, SelectQueryBuilder } from "typeorm";

import type { PageDto, PageResult, INTEGER_PG, ConditionList, KeyAccumulatable, ExtBigIdTuple, BIGINT_PG, KeyBigInt } from "qqlx-core";
import { ENUM_ERROR } from "qqlx-core";
import { toNumber, toString, getPageResultDto, isStringNotValid } from "qqlx-cdk";

import { Logger } from "./log";
import { DaoComAction, EntityMatch } from "./dao.com.interface";
import { DaoOption, DaoBase } from "./dao.com.base";

type Identity = BIGINT_PG;
export class PgDaoBigId<T extends ExtBigIdTuple> extends DaoBase<T> implements DaoComAction<T, Identity> {
  constructor(option: DaoOption<T>) {
    super(option);
  }

  // ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░░░▄▄▄▄▄▄░░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░▄█▀▀▀▀▀█▄░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░▄█░░▄▄▄░░█▄░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▀▀█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▀▀█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▄▄█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░▀▄▀▄▄▄▄▄▀▄▀░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ Calculation

  async count(conditionLs: ConditionList<T>): Promise<number> {
    if (conditionLs.length === 0) {
      throw new Error("缺少查询条件");
    }

    const qb_clean = this.getQueryBuilder();
    this.setCondition(qb_clean, conditionLs, false);

    // 主流程❗❗❗❗❗ 注意不同主键的差异
    const { calc_result } = await qb_clean.select(`COUNT(DISTINCT ${this.relations_name}.id)`, "calc_result").getRawOne();
    return toNumber(calc_result);
  }

  async sum(key: KeyAccumulatable<T>, conditionLs: ConditionList<T>): Promise<INTEGER_PG> {
    if (conditionLs.length === 0) {
      throw new Error("缺少查询条件");
    }

    const qb = this.getQueryBuilder();
    this.setCondition(qb, conditionLs, false);

    const { calc_result } = await qb.select(`SUM("${toString(key)}")`, "calc_result").getRawOne();
    return toNumber(calc_result);
  }

  async sumBigInt(key: KeyBigInt<T>, conditionLs: ConditionList<T>): Promise<BIGINT_PG> {
    if (conditionLs.length === 0) {
      throw new Error("缺少查询条件");
    }

    const qb = this.getQueryBuilder();
    this.setCondition(qb, conditionLs, false);

    const { calc_result } = await qb.select(`SUM("${toString(key)}")`, "calc_result").getRawOne();
    return toString(calc_result, "0");
  }

  // ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░░░▄▄▄▄▄▄░░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░▄█▀▀▀▀▀█▄░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░▄█░░▄▄▄░░█▄░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▀▀█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▀▀█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▄▄█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░▀▄▀▄▄▄▄▄▀▄▀░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ Delete

  async setDisableTrue(identity: Identity) {
    return this.updateOne(identity, { isDisabled: true } as Partial<T>);
  }

  async setDisableFalse(identity: Identity) {
    return this.updateOne(identity, { isDisabled: false } as Partial<T>);
  }

  async deleteOneById(identity: Identity) {
    this.verifyIdentity(identity);

    /** 尽量不要用，删除后记录总数肯定比主键数字大 */
    const queryBuilder = this.getQueryBuilder();
    const result = await queryBuilder
      .delete()
      .from(this.relations_name)

      // ❗❗❗❗❗ 注意不同主键的差异
      .where(`${this.relations_name}.id = :identity`, { identity: toString(identity) })
      .execute();

    // 检查删除是否成功，可以根据返回的结果进行相应的处理
    if (result.affected !== 1) {
      throw { message: `删除失败（${this.relations_name}=${identity}）` };
    }
  }

  // ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░░░▄▄▄▄▄▄░░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░▄█▀▀▀▀▀█▄░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░▄█░░▄▄▄░░█▄░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▀▀█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▀▀█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▄▄█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░▀▄▀▄▄▄▄▄▀▄▀░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ Update

  async updateOne(identity: Identity, updating: Partial<T>): Promise<T> {
    this.verifyIdentity(identity);
    const exist = await this.findOneById(identity);

    delete updating.id; // ❗❗❗❗❗ 注意不同主键的差异
    delete updating.timeUpdate;
    updating.timeUpdate = Date.now().toString();

    const updated = this.repository.merge(exist, updating as DeepPartial<T>);
    const { id } = await this.repository.save(updated);
    return this.findOneById(id);
  }

  /** 找不到更新源，不会报错 */
  async updateOneSafe(identity: Identity, updating: Partial<T>) {
    try {
      await this.updateOne(identity, updating);
    } catch (error) {
      Logger.error({ log: "qqlx-sdk:updateOneSafe", detail: { identity, updating, error } });
    }
  }

  verifyIdentity(identity: Identity) {
    if (isStringNotValid(identity)) {
      throw {
        code: ENUM_ERROR.UNEXPECT_IDENTITY,
        message: this.relations_name.replace(/_/g, ":"),
      };
    }
  }

  // ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░░░▄▄▄▄▄▄░░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░▄█▀▀▀▀▀█▄░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░▄█░░▄▄▄░░█▄░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▀▀█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▀▀█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▄▄█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░▀▄▀▄▄▄▄▄▀▄▀░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ Read

  async page(op: PageDto<T>, qb?: SelectQueryBuilder<T>): Promise<PageResult<T>> {
    const { conditionList } = op;
    const { page, pageSize } = op.page;
    if (pageSize > 1000) throw { message: `最多查询 ${pageSize} 条数据` };

    if (conditionList.length === 0) {
      return getPageResultDto<T>();
    }

    const qb_with_join = qb || this.getQueryBuilder();
    this.setCondition(qb_with_join, conditionList);

    // 主流程
    const [total, list] = await Promise.all([
      this.count(conditionList),
      qb_with_join
        .skip((page - 1) * pageSize)
        .take(pageSize)
        .getMany(),
    ]);

    return { total, list };
  }

  async queryMatch(conditionLs: ConditionList<T>, qb?: SelectQueryBuilder<T>): Promise<EntityMatch<T>> {
    if (conditionLs.length === 0) {
      throw new Error("缺少查询条件");
    }

    const _qb = qb || this.getQueryBuilder();
    this.setCondition(_qb, conditionLs, false);
    const [total, first] = await Promise.all([this.count(conditionLs), _qb.take(1).getOne()]);
    return {
      count: total,
      firstMatch: first ?? undefined,
    };
  }

  async queryAll(conditionLs: ConditionList<T>, qb?: SelectQueryBuilder<T>): Promise<PageResult<T>> {
    if (conditionLs.length === 0) {
      throw new Error("缺少查询条件");
    }

    const qb_with_join = qb || this.getQueryBuilder();
    this.setCondition(qb_with_join, conditionLs);

    // 主流程
    const [total, list] = await Promise.all([this.count(conditionLs), qb_with_join.getMany()]);
    return { total, list };
  }

  async findManyById(identityList: Identity[]): Promise<T[]> {
    const queryBuilder = this.getQueryBuilder();
    return queryBuilder.whereInIds(identityList).getMany();
  }

  async findOneById(identity: Identity): Promise<T> {
    this.verifyIdentity(identity);
    const qb = this.getQueryBuilder();

    // ❗❗❗❗❗ 注意不同主键的差异
    const sql = `${this.relations_name}.id = :identity`;
    qb.where(sql, { identity: toString(identity) });

    const result = await qb.getOne();
    if (!result) throw { code: ENUM_ERROR.NOT_FOUND_ENTITY };
    return result as T;
  }

  // ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░░░▄▄▄▄▄▄░░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░▄█▀▀▀▀▀█▄░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░▄█░░▄▄▄░░█▄░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▀▀█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▀▀█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░█░░█▄▄█░░█░░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░▀▄▀▄▄▄▄▄▀▄▀░░░░░░░░░░░░░░░░░░░░░░░░░░
  // ░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░ Create

  async insertOne(schema: T): Promise<T> {
    const _schema = this.repository.create(schema);
    delete (_schema as Partial<T>).id;

    const { id } = await this.repository.save(_schema);
    return this.findOneById(id);
  }

  async insertMany(schemas: T[]): Promise<Identity[]> {
    const _s = this.repository.create(schemas);
    _s.forEach((__s) => delete (__s as Partial<T>).id);

    const entities = await this.repository.save(_s);
    return entities.map((e) => e.id);
  }

  async insertManyAndQuery(schemas: T[]): Promise<T[]> {
    const ids = await this.insertMany(schemas);
    return this.findManyById(ids);
  }
}
